/* tibet.c -- Tiny BDF font file editor				(TLP-01)
   Copyright (C) 1999 Electrotechnical Laboratory, JAPAN.
   Author: Ken'ichi HANDA <handa@etl.go.jp>  */

/* TIBET is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.  */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

#ifndef LIBDIR			/* Makefile should supply this macro.  */
#define LIBDIR "/usr/local/share/tibet"
#endif
#ifndef VERSION			/* Makefile should supply thisp macro.  */
#define VERSION "?.?"
#endif
#define BITMAP "bitmap -grid -size 8x8"
#define MENU_FONT "7x14"

/* Information of each character */
typedef struct _charinfo {
  char *startchar;				/* STARTCHAR */
  int swidth_x, swidth_y, dwidth_x, dwidth_y;	/* SWIDTH, DWIDTH */
  int w, h, x, y;				/* BBX */
  unsigned char *bitmap;			/* BITMAP */
  int editing;					/* 1 while editing */
} charinfo;

#define BMP(col) ch->bitmap[bytes * j + (col)]

char *head = NULL;		/* HEADER is stored here.  */
charinfo *chartbl[0x10000];	/* table of pointers to the above structure */
charinfo *ch;			/* temporary working pointer */

/* General information of the editing BDF font */
char *bdfname = NULL;		/* name of the BDF file */
int backuped = 0;		/* 1 iff the BDF file is already backuped */
struct bbxinfo { int w, h, x, y; } f;/* FONTBOUNDINGBOX */
int chars;			/* CHARS */
int line_bytes;			/* same as (f.w + 7) / 8; */
char *charset = NULL;		/* CHARSET_REGISTRY field of the font name */
int min_idx, max_idx, cur_idx, min_page, max_page, cur_page;
int copy_idx;			/* Index of a copied character. */
int modified = 0;		/* 1 iff the current font is modified */

/* X related global variables.  */
Display *disp;
int screen;
Window win;
XGCValues gcv;
struct { GC norm, light, mid, dark, rev, light_inv, dark_inv, temp; } gc;
XEvent event;
KeySym keysym;
XFontStruct *font;
Pixmap pixmap;

/* global variables */
char workdir[FILENAME_MAX+1];	/* name of working directory */
char linebuf[1024*16];		/* line buffer for general use */
FILE *fp;			/* file pointer for general use */

/* X related macros.  */
#define FW (font->max_bounds.width)
#define FH (font->max_bounds.ascent + font->max_bounds.descent)
#define FASCENT (font->max_bounds.ascent)

#define X_GC(gc, fore, back)						\
  (gc = XCreateGC (disp, RootWindow (disp, screen), 0L, NULL),		\
   XSetForeground (disp, gc, fore), XSetBackground (disp, gc, back),	\
   XSetFont (disp, gc, font->fid))

#define X_Clear(x, y, w, h) XClearArea (disp, win, x, y, w, h, False)

#define X_Draw(gc, x, y, str, len) \
  XDrawImageString (disp, win, gc, x, y, str, len < 0 ? strlen (str) : len)

#define X_Fill(gc, x, y, w, h) XFillRectangle (disp, win, gc, x, y, w, h)

#define DRAW_MENU(i)					\
  X_Draw ((i) == (-1 - cur_idx) ? gc.rev : gc.norm,	\
	  MENU_X0, MENU_Y0 + LINE_HEIGHT * i + FASCENT, menu[i].message, -1)

/* misc macros */
#define COLS		16	/* number of columns of bitmap area */
#define ROWS		8	/* number of rows of bitmap area */
#define PAGESIZE (COLS * ROWS)	/* number of characers in a page */
#define PAGE_HEAD(idx) ((idx) / PAGESIZE)
#define INVALID_IDX -100

#define FATAL(result, fmt, arg) (fprintf (stderr, fmt, arg), exit (result), 0)

#define WARNING(bell, return_flag, fmt, arg)	\
  do {						\
    char buf[1024];				\
    sprintf (buf, fmt, (arg));			\
    show_message (buf, 0);			\
    if (bell) XBell (disp, 0);			\
    if (return_flag) return return_flag;	\
  } while (0)

#define NEWCPY(from, type, dist, len) \
  (dist = type malloc (len), memcpy (dist, from, len))

char hex[256];			/* hex character to hex value */
unsigned char rev_bits[16] = {	/* Big endian <-> Small endian */
  0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 };

/* Scan a decimal integer at *STR and store it at *VAL.  */
getd (int *val, char **str)
{
  register unsigned char *p = (unsigned char *) *str;
  register int n = 0, sign = 1, i;

  while ((i = hex[*p]) == -1 || i > 9) if (!*p++) return 0;
  if (i == -2) sign = -1, p++;
  while ((i = hex[*p++]) >= 0 && i < 10) n = n * 10 + i;
  *str = --p, *val = n * sign;
  return 1;
}

create_bdf (char *fbbx)
{
  if (sscanf (fbbx, "%d%d%d%d", &f.w, &f.h, &f.x, &f.y) != 4)
    return -1;
  bdfname = (char *) malloc (strlen (fbbx) + 5);
  sprintf (bdfname, "%s.bdf", fbbx);
  if (!(fp = fopen (bdfname, "w")))
    FATAL (1, "Can't write to %s\n", bdfname);
  sprintf (linebuf, "STARTFONT 2.1\n\
FONT -new-fixed-medium-r-normal--%d-%d-72-72-C-%d-new-0\n\
SIZE %d 72 72\n\
FONTBOUNDINGBOX %d %d %d %d\n",
	   f.h, f.h * 10, f.w * 10, f.h, f.w, f.h, f.x, f.y);
  fprintf (fp, "%sCHARS 0\nENDFONT\n", linebuf);
  fclose (fp);
  NEWCPY (linebuf, (char *), head, strlen (linebuf) + 1);
  line_bytes = (f.w + 7) / 8;
  chars = 0, min_idx = max_idx = min_page = 0, max_page = 1;
  return 0;
}

read_bdf (int fd)
{
  int size, i, j;
  char *addr, *p, *p1, *p2,*pend;
  struct stat statbuf;

  fstat (fd, &statbuf);
  size = statbuf.st_size;
  if ((int) (p = (char *) mmap (0, size, PROT_READ, MAP_PRIVATE, fd, 0)) == -1)
    FATAL (1, "mmap failed!", "");
  close (fd);

  addr = p; chars = f.w = -1;
  do {
    if (!strncmp (p, "FONTBOUNDINGBOX", 15))
      getd (&f.w, &p), getd (&f.h, &p), getd (&f.x, &p), getd (&f.y, &p);
    else if (!strncmp (p, "CHARS", 5))
      p1 = p, getd (&chars, &p), p2 = p + 1;
    else if (!charset && !strncmp (p, "FONT ", 5))
      {
	pend = strchr (p, '\n');
	for (i = 0; p && p + 1 < pend && i < 13; p = strchr (p + 1, '-'), i++);
	if (p && i == 13)
	  {
	    charset = (char *) malloc (pend - p);
	    for (i = 0, p++; p < pend; i++, p++) charset[i] = tolower (*p);
	    charset[i] = '\0';
	  }
	p = pend;
      }
    if (!(p = strchr (p, '\n'))) break;
    p++;
  } while (strncmp (p, "STARTCHAR", 9) && strncmp(p, "ENDFONT", 7));
  p || FATAL (1, "No STARTCHAR nor ENDFONT in BDF file\n", NULL);
  f.w >= 0 || FATAL (1, "No FONTBOUNDINGBOX in BDF file\n", NULL);

  if (f.y > 0) f.y *= -1;	/* Some fonts contain wrong BBX_Y value.  */
  line_bytes = (f.w + 7) / 8;
  head = (char *) malloc ((p - p2) + (p1 - addr) + 1);
  strncpy (head, addr, p1 - addr), strncat (head, p2, p - p2);
  chars = 0, min_idx = 0x10000, max_idx = 0;

  while (!strncmp (p, "STARTCHAR", 9))
    {
      int idx = -1, len;

      chars++;
      ch = (charinfo *) malloc (sizeof (charinfo));
      p += 10;
      len = strchr (p, '\n') - p;
      NEWCPY (p, (char *), ch->startchar, len + 1);
      ch->startchar[len] = '\0';
      ch->editing = 0;
      ch->w = -1;

      p += len + 1;
      while (strncmp (p, "ENDCHAR", 7))
	{
	  if (!strncmp (p, "ENCODING", 8))
	    (p += 8, getd (&idx, &p))
	      || FATAL (1, "Invalid ENCODING in %s\n", ch->startchar);
	  else if (!strncmp (p, "BBX", 3))
	    (p += 3, getd (&ch->w, &p) && getd (&ch->h, &p)
	     && getd (&ch->x, &p) && getd (&ch->y, &p))
	      || FATAL (1, "Invalid BBX in %s\n", ch->startchar);
	  else if (!strncmp (p, "BITMAP", 6))
	    {
	      int bytes = (ch->w + 7) / 8;

	      if (ch->w < 0) FATAL (1, "No BBX in %s\n", ch->startchar);
	      ch->bitmap = (unsigned char *) malloc (bytes * ch->h);
	      for (j = 0, p += 5; j < ch->h; j++)
		{
		  p = strchr (p, '\n') + 1;
		  for (i = 0; i < bytes && *p != '\n'; i++, p += 2)
		    BMP (i) = (rev_bits[hex[*p]]
			       + rev_bits[hex[*(p + 1)]] * 16);
		}
	    }
	  else if (!strncmp (p, "SWIDTH", 6))
	    (p += 6, getd (&ch->swidth_x, &p) && getd(&ch->swidth_y, &p))
	      || FATAL (1, "Invalid SWIDTH in %s\n", ch->startchar);
	  else if (!strncmp (p, "DWIDTH", 5))
	    (p += 6, getd (&ch->dwidth_x, &p) && getd(&ch->dwidth_y, &p))
	      || FATAL (1, "Invalid DWIDTH in %s\n", ch->startchar);
	  else
	    FATAL (1, "Invalid record in %s\n", ch->startchar);
	  p = strchr (p, '\n') + 1;
	}

      idx >= 0 || FATAL (1, "No ENCODING in\n%s", ch->startchar);
      ch->bitmap || FATAL (1, "No BITMAP in\n%s", ch->startchar);
      (ch->swidth_x > 0) || (ch->swidth_x = 1000);
      (ch->dwidth_x > 0) || (ch->dwidth_x = f.w);
      chartbl[idx] = ch;
      if (idx < min_idx) min_idx = idx;
      else if (idx > max_idx) max_idx = idx;
      p = strchr (p, '\n') + 1;
    }
  munmap (addr, size);
  if (min_idx == 0x10000) min_idx = 0;
  min_page = PAGE_HEAD (min_idx), max_page = PAGE_HEAD (max_idx) + 1;
}

write_bdf ()
{
  int idx, bytes, i;

  if (!backuped++)
    {
      sprintf (linebuf, "/bin/cp %s %s.old", bdfname, bdfname);
      system (linebuf);
    }
  if ((fp = fopen (bdfname, "w")) == NULL)
    WARNING (1, -1, "Can't write to \"%s\"", bdfname);
  WARNING (0, 0, "Saving %s ...", bdfname);
  fprintf (fp, "%sCHARS %d\n", head, chars);
  for (idx = min_idx; idx <= max_idx; idx++)
    if (ch = chartbl[idx])
      {
	fprintf (fp, "STARTCHAR %s\nENCODING %d\n", ch->startchar, idx);
	fprintf (fp, "SWIDTH %d %d\nDWIDTH %d %d\nBBX %d %d %d %d\nBITMAP",
		 ch->swidth_x, ch->swidth_y, ch->dwidth_x, ch->dwidth_y,
		 ch->w, ch->h, ch->x, ch->y);
	bytes = (ch->w + 7) / 8;
	for (i = 0; i < bytes * ch->h; i++)
	  {
	    unsigned char bits = ch->bitmap[i];
	    if (!(i % bytes)) fprintf (fp, "\n");
	    fprintf (fp, "%1x%1x", rev_bits[bits & 0xF], rev_bits[bits >> 4]);
	  }
	fprintf (fp, "\nENDCHAR\n");
      }
  fprintf (fp, "ENDFONT\n");
  fclose (fp);
  WARNING (0, 0, "Saving %s ... done", bdfname);
  modified = 0;
}

update_range (int idx)
{
  if (idx < min_idx) min_idx = idx;
  else if (idx > max_idx) max_idx = idx;
  min_page = PAGE_HEAD (min_idx), max_page = PAGE_HEAD (max_idx) + 1;
  modified = 1;
}

read_xbm (int idx)
{
  int w, h, x, y;
  unsigned char *bitmap;

  sprintf (linebuf, "%s/c%d.xbm", workdir, idx);
  if (XReadBitmapFileData (linebuf, &w, &h, &bitmap, &x, &y) != BitmapSuccess)
    return -1;

  if ((ch = chartbl[idx]) != NULL)
    free (ch->bitmap);
  else
    {
      chartbl[idx] = (ch = (charinfo *) malloc (sizeof (charinfo)));
      ch->startchar = (char *) malloc (6);
      sprintf (ch->startchar, "c%02x", idx);
      ch->swidth_x = 1000 * ch->w / f.h, ch->swidth_y = 0;
      ch->dwidth_x = ch->w, ch->dwidth_x = 0;
    }
  ch->w = w, ch->h = h, ch->x = x, ch->y = y - h;
  NEWCPY (bitmap, (unsigned char *), ch->bitmap, ((w + 7) / 8) * h);
  XFree (bitmap);
  ch->editing = 0;
  update_range (idx);
  return 0;
}

write_xbm (int idx)
{
  ch = chartbl[idx];
  regularize_bitmap (ch);
  pixmap = XCreateBitmapFromData (disp, win, ch->bitmap, ch->w, ch->h);
  sprintf (linebuf, "%s/c%d.xbm", workdir, idx);
  if (XWriteBitmapFile (disp, linebuf, pixmap,
			ch->w, ch->h, ch->x, ch->h + ch->y) != BitmapSuccess)
    WARNING (1, 0, "Write %s failed!", linebuf);
  XFreePixmap (disp, pixmap);
}

make_char (int idx)
{
  ch = (chartbl[idx] = (charinfo *) malloc (sizeof (charinfo)));
  ch->w = f.w, ch->h = f.h, ch->x = f.x, ch->y = f.y;
  ch->swidth_x = 1000 * ch->w / f.h, ch->swidth_y = 0;
  ch->dwidth_x = ch->w, ch->dwidth_x = 0;
  ch->startchar = (char *) malloc (6);
  sprintf (ch->startchar, "c%02x", idx);
  ch->bitmap = (unsigned char *) malloc (line_bytes * ch->h);
  memset (ch->bitmap, 0, line_bytes * ch->h);
  ch->editing = 0;
  if (chars++ == 0) min_idx = max_idx = idx;
  update_range (idx);
}

delete_char (int idx)
{
  ch = chartbl[idx];
  write_xbm (idx);
  free (ch->startchar), free (ch->bitmap), free (ch);
  chartbl[idx] = NULL;
  chars--;
  if (idx == min_idx) while (!chartbl[++min_idx]);
  else if (idx == max_idx) while (!chartbl[--max_idx]);
  min_page = PAGE_HEAD (min_idx), max_page = PAGE_HEAD (max_idx) + 1;
  modified = 1;
}

yank_char (int from, int to, int merge)
{
  int i, j = f.h * line_bytes;

  if (!merge && chartbl[to]) delete_char (to);
  if (!chartbl[to]) make_char (to);
  else regularize_bitmap (chartbl[to]);
  regularize_bitmap (chartbl[from]);
  for (i = 0; i < j; i++) chartbl[to]->bitmap[i] |= chartbl[from]->bitmap[i];
  draw_xbm (to, 0, 0);
  modified = 1;
}

max_space (int dir, int from, int to)
{
  int space = 8, bytes = line_bytes, i, j, k;

  if (dir == 'r' || dir == 'l')
    {
      for (i = from; i <= to && space > 0; i++)
	if (ch = chartbl[i])
	  for (j = 0; j < ch->h; j++)
	    {
	      unsigned char bits = BMP (dir == 'l' ? 0 : bytes - 1);
	      int this_line_space = 8;
	      if (dir == 'l') while (bits) this_line_space--, bits <<= 1;
	      else while (bits) this_line_space--, bits >>= 1;
	      if (this_line_space < space) space = this_line_space;
	    }
    }
  else
    {
      for (i = from; i <= to && space > 0; i++)
	if (ch = chartbl[i])
	  for (k = 0; k < bytes; k++)
	    {
	      int this = 0;
	      if (dir == 'u') for (j = 0 ; j < ch->h && !BMP (k); j++) this++;
	      else for (j = ch->h - 1; j >= 0 && !BMP (k); j--) this++;
	      if (this < space) space = this;
	    }
    }
  return (dir == 'r' ? space - (8 - (f.w % 8)) : space);
}

shift_glyph (int idx, int dir)
{
  chartbl[idx]->x -= (dir == 'l' ? 1 : dir == 'r' ? -1 : 0);
  chartbl[idx]->y += (dir == 'u' ? 1 : dir == 'd' ? -1 : 0);
  regularize_bitmap (ch);
  draw_xbm (idx, 0, 0);
  modified = 1;
}

bold_glyph (int idx)
{
  unsigned char *bmp;
  int bytes = line_bytes, i, j;

  ch = chartbl[idx];
  regularize_bitmap (ch);
  bmp = ch->bitmap;
  for (j = 0; j < ch->h; j++)
    for (i = ch->w - 1; i > 0; i--)
      if (BMP ((i - 1) / 8) & (1 << ((i - 1) % 8)))
	BMP (j / 8) |= 1 << (j % 8);
  draw_xbm (idx, 0, 0);
  modified = 1;
}

italic_glyph (int idx)
{
  unsigned char *bmp;
  int bytes = line_bytes, i, j, rows;

  ch = chartbl[idx];
  regularize_bitmap (ch);
  bmp = ch->bitmap;
  rows = ch->h / 5;
  for (j = 0; j < rows * 2; j++)
    {
      int bits = 2 - (j / rows);
      for (i = (ch->w - 1) / 8; i > 0; i--)
	BMP (i) = (BMP (i) << bits) | (BMP (i - 1) >> (8 - bits));
      BMP (i) <<= bits;
    }
  for (j = rows * 3; j < ch->h; j++)
    {
      int bits = (j / rows) - 2;
      for (i = 0; i < (ch->w - 1) / 8; i++)
	BMP (i) = (BMP (i) >> bits) | (BMP (i + 1) << (8 - bits));
      BMP (i) >>= bits;
    }
  draw_xbm (idx, 0, 0);
  modified = 1;
}

int sep = 6;			/* at least 6 dots for item separation  */
#define INNER		10	/* internal border width */
#define LINE_HEIGHT	(FH + sep)
#define BMP_X0 (INNER)
#define BMP_X1 (BMP_X0 + (f.w + sep) * COLS)
#define BMP_Y0 (INNER + LINE_HEIGHT * 2)
#define BMP_Y1 (BMP_Y0 + (f.h + sep) * ROWS)
#define BMP_WIDTH (BMP_X1 - sep - BMP_X0)
#define BMP_HEIGHT (BMP_Y1 - sep - BMP_Y0)
#define MENU_X0 (BMP_X1 + INNER)
#define MENU_X1 (MENU_X0 + FW * 4)
#define MENU_Y0 INNER
#define MENU_Y1 (MENU_Y0 + LINE_HEIGHT * ((sizeof menu) / (sizeof menu[0])))
#define WIN_WIDTH  (MENU_X1 + INNER)
#define WIN_HEIGHT (BMP_Y1 + FH + INNER)

/* Show the message STR at the message area of window.  If STR is NULL, the
   message area is just erased.  If WAIT != 0, wait user key/click.  */
show_message (char *str, int wait)
{
  X_Clear (BMP_X0, BMP_Y1, MENU_X1 - BMP_X0, FH);
  if (str) X_Draw (gc.norm, BMP_X0, BMP_Y1 + FASCENT, str, -1);
  if (wait) x_next_event (NULL, 1);
  if (wait < 2) return;
  X_Fill (gc.mid, BMP_X0, BMP_Y0, BMP_WIDTH, BMP_HEIGHT);
  X_Clear (BMP_X0, BMP_Y1, MENU_X1 - BMP_X0, FH);
  draw_page (0);
}

/* Go to the next/previous page.  Vacant pages are skipped if FORCE is 0.  */
next_or_prev (int next, int force)
{
  int i = cur_page * PAGESIZE + (next ? PAGESIZE : - 1);

  if (i > max_idx && next && (!force || i >= 0x10000))
    WARNING (1, 1, "No next page", "");
  else if (i <= min_idx && !next && (!force || i < 0))
    WARNING (1, 1, "No previous page", "");
  else if (force)
    cur_page = PAGE_HEAD (i);
  else
    {
      if (next) while (!chartbl[i]) i++;
      else while (!chartbl[i]) i--;
      cur_page = PAGE_HEAD (i);
    }
  if (cur_idx >= 0)
    cur_idx = cur_page * PAGESIZE + (cur_idx % PAGESIZE);
  return draw_page (1);
}

char *help_message[128];		/* Array of lines of help message.  */

show_help ()
{
  int i, y;

  X_Fill (gc.norm, BMP_X0, BMP_Y0, BMP_WIDTH, BMP_HEIGHT);
  for (i = 0, y = BMP_Y0; help_message[i]; i++, y += FH)
    {
      if (help_message[i][0])
	X_Draw (gc.rev, BMP_X0 + sep, y + FASCENT, help_message[i], -1);
      else
	{
	  show_message ("Any key to next page!!", 1);
	  X_Fill (gc.norm, BMP_X0, BMP_Y0, BMP_WIDTH, BMP_HEIGHT);
	  y = BMP_Y0 - FH;
	}
    }
  show_message ("Any key to quit help!!", 2);
}

show_sample ()
{
  int c, x = BMP_X0 + sep, y = BMP_Y0 + sep, maxy = BMP_Y1 - sep;

  sprintf (linebuf, "%s/%s.txt", LIBDIR, charset ? charset : "");
  if (!charset
      || (fp = fopen (linebuf, "r")) == NULL)
    sprintf (linebuf, "%s/iso8859-1.txt", LIBDIR);
  if ((fp = fopen (linebuf, "r")) == NULL)
    WARNING (1, 1, "No sample text for %s", charset ? charset : "this font");
  X_Fill (gc.rev, BMP_X0, BMP_Y0, BMP_WIDTH, BMP_HEIGHT);
  for (c = getc (fp); c != EOF && (y + f.h < maxy); x += f.w, c = getc (fp))
    {
      if (c == '\n' || x + f.w >= BMP_X1 - sep)
	y += f.h, x = BMP_X0 + sep - f.w;
      else
	{
	  if (max_idx > 256) c = c * 256 + getc (fp);
	  if (min_idx > c) c |= 0x8080; /* set MSB */
	  else if (max_idx < c) c &= 0x7F7F; /* reset MSB */
	  if (chartbl[c]) draw_xbm (c, x, y);
	}
    }
  fclose (fp);
  show_message ("Hit any key to dismiss!", 2);
}

struct menu_struct {
  char *message, *key;
} menu[] = {{"NEXT", "nN"}, {"PREV", "pP"}, {"SAVE", "sS"}, {"EXIT", "qq"},
	    {"HELP", "hh"}, {"WIDN", "WW"}, {"SHRK", "ZZ"}, {"SMPL", "!!"} };

/* Check if (X, Y) is in the square area (X0, Y0)-(X1, Y1).  */
#define IN(x, y, x0, x1, y0, y1) \
  ((x) >= (x0) && (x) < (x1) && (y) >= (y0) && (y) < (y1))

/* Return glyph index at (X, Y) if it's in bitmap area.  If (X, Y) is on
   menu area, return a negative value (-1 - N) for Nth menu.  If (X, Y) is
   not in any of those area, return INVALID_IDX.  */
xy_idx (int x, int y)
{
  int idx;

  if (IN (x, y, BMP_X0, BMP_X1, BMP_Y0, BMP_Y1))
    return (cur_page * PAGESIZE
	    + (y - BMP_Y0) / (f.h + sep) * COLS + (x - BMP_X0) / (f.w + sep));
  else if (!IN (x, y, MENU_X0, MENU_X1, MENU_Y0, MENU_Y1))
    return INVALID_IDX;
  idx = (y - MENU_Y0) / LINE_HEIGHT;
  return (y < MENU_Y0 + idx * LINE_HEIGHT + FH
	  ? -1 - idx
	  : INVALID_IDX);
}

/* Pad bitmap to fit in FONTBOUNDINGBOX.  */
regularize_bitmap (charinfo *ch)
{
  unsigned char *bmp = ch->bitmap, *newbmp;
  int bytes = (ch->w + 7) / 8, i, j, x, y;

  if (ch->w == f.w && ch->h == f.h && ch->x == f.x && ch->y == f.y) return;
  newbmp = (unsigned char *) malloc (line_bytes * f.h);
  memset (newbmp, 0, line_bytes * f.h);
  for (i = 0; i < ch->h; i++)
    if ((y = (f.y + f.h) - (ch->y + ch->h - i)) >= 0 && y < f.h)
      for (j = 0; j < ch->w; j++)
	if ((x = ch->x - f.x + j) >= 0 && x < f.w
	    && bmp[bytes * i + (j / 8)] & (1 << (j % 8)))
	  newbmp[line_bytes * y + (x / 8)] |= (1 << (x % 8));
  ch->w = f.w, ch->h = f.h, ch->x = f.x, ch->y = f.y;
  free (bmp);
  ch->bitmap = newbmp;
}

/* Remove top and bottom padding lines (not left and right padding bits).  */
shrink_bitmap (charinfo *ch)
{
  unsigned char *bmp = ch->bitmap, *newbmp;
  int bytes = (ch->w + 7) / 8, from = 0, to = ch->h, i, j = bytes * ch->h;

  for (i = 0; i < j; i++) if (bmp[i] != 0) { from = i / bytes; break; }
  for (i = j - 1; i >=0; i--) if (bmp[i] != 0) { to = (i / bytes) + 1; break; }
  if (from == 0 && to == ch->h) return;
  NEWCPY (bmp + from * bytes, (unsigned char *), newbmp, bytes * (to - from));
  ch->y += ch->h - to, ch->h = to - from;
  free (bmp);
  ch->bitmap = newbmp;
}

/* Draw IDXth glyph at (X, Y).  If X is 0, the location is desided by IDX.  */
draw_xbm (int idx, int x, int y)
{
  ch = chartbl[idx];
  if (x == 0) (x = BMP_X0 + (sep + f.w) * (idx % COLS),
	       y = BMP_Y0 + (sep + f.h) * ((idx % PAGESIZE) / COLS));
  if (x < BMP_X1 && idx == cur_idx)
    {
      if (ch)
	sprintf (linebuf, "%02X (%d,%d,%d,%d) %s",
		 cur_idx, ch->w, ch->h, ch->x, ch->y, ch->startchar);
      else
	sprintf (linebuf, "%02x NO CHARACTER", cur_idx);
      linebuf[BMP_WIDTH / FW] = '\0';
      X_Clear (INNER, INNER + FH + sep, BMP_WIDTH, FH);
      X_Draw (gc.norm, BMP_X0, INNER + LINE_HEIGHT + FASCENT, linebuf, -1);
      gc.temp = (ch && !ch->editing) ? gc.rev : gc.dark;
    }
  else
    gc.temp = (ch && (x > BMP_X1 || !ch->editing)) ? gc.norm : gc.light;

  if (!ch)
    X_Fill (gc.temp, x, y, f.w, f.h);
  else if (ch->w != f.w || ch->h != f.h || ch->x != f.x || ch->y != f.y)
    X_Fill (cur_idx == idx ? gc.dark : gc.light, x, y, f.w, f.h);
  if (!ch || ch->w == 0 || ch->h == 0) return;
  pixmap = XCreateBitmapFromData (disp, win, ch->bitmap, ch->w, ch->h);
  if (gc.temp == gc.dark) gc.temp = gc.dark_inv;
  else if (gc.temp == gc.light) gc.temp = gc.light_inv;
  XCopyPlane (disp, pixmap, win, gc.temp, 0, 0, ch->w, ch->h,
	      x + ch->x - f.x, y + (f.h + f.y) - (ch->h + ch->y), 1L);
  XFreePixmap (disp, pixmap);
}

int drawing_interrupted = -1;

draw_page (int quitp)
{
  int len = (cur_page * PAGESIZE > 256) ? 4 : 2, x, w, i;

  X_Clear (BMP_X0, INNER, BMP_WIDTH, FH);
  sprintf (linebuf, "%02X", cur_page * PAGESIZE);
  x = (cur_page <= min_page
       ? 0 : (BMP_WIDTH * (cur_page - min_page)
	      / ((cur_page > max_page ? cur_page : max_page) - min_page)));
  w = ((cur_page + 1 < max_page)
       ? BMP_WIDTH / (max_page - min_page) : BMP_WIDTH - x);
  X_Fill (gc.norm, BMP_X0 + x, INNER, w, FH);
  if (x > BMP_WIDTH - FW * len) x = BMP_WIDTH - FW * len;
  X_Draw (gc.rev, BMP_X0 + x, INNER + FASCENT, linebuf, len);

  X_Fill (gc.mid, BMP_X0, BMP_Y0, BMP_WIDTH, BMP_HEIGHT);
  for (i = 0; i < PAGESIZE; draw_xbm (cur_page * PAGESIZE + i++, 0, 0))
    if (quitp && XCheckWindowEvent (disp, win, KeyPressMask, &event) == True)
      return (drawing_interrupted = -1);
  return (drawing_interrupted = 0);
}

/* Initialize the window  */
init_x (char *dispname)
{
  int i, j, k, help_len = 0, help_lines = 0;
  unsigned long white, black;
  XColor exact_def;
  XSetWindowAttributes winattr;
  Colormap cmap;

  if (!(disp = XOpenDisplay (dispname)))
    FATAL (1, "Display %s can't be opened.\n", dispname);
  screen = DefaultScreen (disp);
  if (DisplayPlanes (disp, screen) == 1)
    FATAL (1, "Can't run only on monochrome screen.\n", "");
  white = WhitePixel (disp, screen);
  black = BlackPixel (disp, screen);
  if (!(font = XLoadQueryFont (disp, MENU_FONT)))
    FATAL (1, "Can't open font %s.\n", MENU_FONT);

  sprintf (linebuf, "%s/tibet.hlp", LIBDIR);
  if ((fp = fopen (linebuf, "r")) == NULL)
    FATAL (1, "Can't open help file:%s.\n", linebuf);
  for (i = j = 0; fgets (linebuf, sizeof (linebuf), fp) != NULL; i++)
    {
      linebuf[k = strlen (linebuf) - 1] = '\0';
      NEWCPY (linebuf, (char *), help_message[i], k + 1);
      if (linebuf[0] == '\0')
	help_lines = (j > help_lines ? j : help_lines), j = 0;
      else
	help_len = (k > help_len ? k : help_len), j++;
    }

  while (BMP_WIDTH < help_len * FW + sep * 2) sep++;
  while (BMP_HEIGHT < help_lines * FH) sep++;

  win = XCreateSimpleWindow (disp, RootWindow (disp, screen),
			     0, 0, WIN_WIDTH, WIN_HEIGHT, 0, 0, white);
  winattr.backing_store = Always;
  XChangeWindowAttributes (disp, win, CWBackingStore, &winattr);
  XSelectInput (disp, win, ButtonPressMask|KeyPressMask|PointerMotionMask);
  XStoreName (disp, win, "tibet");
  X_GC (gc.norm, black, white);
  X_GC (gc.rev, white, black);
  cmap = DefaultColormap (disp, screen);
  XParseColor (disp, cmap, "gray80", &exact_def);
  XAllocColor (disp, cmap, &exact_def);
  X_GC (gc.light, exact_def.pixel, black);
  X_GC (gc.light_inv, black, exact_def.pixel);
  XParseColor (disp, cmap, "gray65", &exact_def);
  XAllocColor (disp, cmap, &exact_def);
  X_GC (gc.mid, exact_def.pixel, white);
  XParseColor (disp, cmap, "gray50", &exact_def);
  XAllocColor (disp, cmap, &exact_def);
  X_GC (gc.dark, exact_def.pixel, white);
  X_GC (gc.dark_inv, white, exact_def.pixel);
  XMapWindow (disp, win);

  X_Fill (gc.mid, 0, 0, WIN_WIDTH, WIN_HEIGHT);
  for (i = 0; i < (sizeof menu) / (sizeof menu[0]); i++) DRAW_MENU (i);
  X_Clear (BMP_X0, BMP_Y1, MENU_X1 - BMP_X0, FH);
}

/* Read the next X event, return ASCII code on KeyPress while
   concering KEYSYM_MAP, or a negative value on the other events.
   NOMOTION 1 means ignore MotionNotify.  */
x_next_event (int *keysym_map, int nomotion)
{
  do {
    XNextEvent (disp, &event);
    switch (event.type) {
    case MotionNotify: if (nomotion) continue; else return -1;
    case ButtonPress: return ((event.xbutton.button == Button1) - 3);
    case KeyPress:
      XLookupString (&event.xkey, linebuf, 1, &keysym, NULL);
      if (keysym >= XK_space && keysym <= XK_asciitilde) return linebuf[0];
      if (keysym_map)
	for (; *keysym_map; keysym_map += 2)
	  if (keysym == *keysym_map) return *(keysym_map + 1);
    }
  } while (1);
}

/* Interactively get YES or NO answer for the message STR.  */
x_y_or_n (char *str)
{
  int w = FW * strlen (str);
  int x = (BMP_X0 + BMP_X1) / 2, y = (BMP_Y0 + BMP_Y1) / 2;
  int x0 = x - FW * 4, x1 = x - FW, x2 = x + FW, x3 = x + FW * 3;
  int y0 = y + FH / 2, y1 = y0 + FH;
  int state= 0, new, n, key;

  X_Fill (gc.norm, x - w / 2 - FW, y - FH * 3 / 2, w + FW * 2, FH * 3);
  X_Draw (gc.rev, x - w / 2, y - FH + FASCENT, str, -1);
  X_Draw (gc.rev, x0, y + FASCENT, "YES", 3);
  X_Draw (gc.rev, x2, y + FASCENT, "NO", 2);
  XWarpPointer (disp, 0, win, 0, 0, 0, 0, x, y + FH);
  while (state < 3)
    switch (key = x_next_event (NULL, 0)) {
    case -1:			/* MotionNotify */
      if (IN (event.xbutton.x, event.xbutton.y, x0, x1, y0, y1)) new = 1;
      else if (IN (event.xbutton.x, event.xbutton.y, x2, x3, y0, y1)) new = 2;
      else new = 0;
      if (state == new) continue;
      state = new;
      X_Draw ((state == 1 ? gc.norm : gc.rev), x0, y + FASCENT, "YES", 3);
      X_Draw ((state == 2 ? gc.norm : gc.rev), x2, y + FASCENT, "NO", 2);
      break;
	  
    case -2: case -3:		/* ButtonPress */
      if (state) state += 2;
      break;

    default:			/* KeyPress */
      if (key == 'Y' || key == 'y') state = 3;
      else if (key == 'N' || key == 'n') state = 4;
    }
  X_Fill (gc.mid, x - w / 2 - FW, y - FH * 3 / 2, w + FW * 2, FH * 3);
  draw_page (0);
  return (4 - state);
}

int main_keysym_map[] = {
  XK_Home, '<', XK_Left, 'l', XK_Up, 'u', XK_Right, 'r', XK_Down, 'd',
  XK_Prior, 'p', XK_Next, 'n', XK_End, '>', XK_Begin, '<',
  XK_BackSpace, 'x', XK_Delete, 'x', XK_Insert, 'i', XK_Escape, 'q', 0 };

main (int argc, char **argv)
{
  int idx, key, x, y, i, fd, end_flag = 0;
  char *dispname = NULL;

  for (i = 1; i < argc; i++)
    {
      if (!strcmp (argv[i], "-display") && i + 1 < argc)
	dispname = argv[++i];
      else if (!strncmp (argv[i], "-h", 2))
	FATAL (0, "Usage: %s [-v] [-h] [BDFFILE | W+H+X-Y]\n", argv[0]);
      else if (!strncmp (argv[i], "-v", 2))
	FATAL (0, "TIBET Version %s\n", VERSION);
      else
	bdfname = argv[i];
    }
  dispname || (dispname = (char *) getenv ("DISPLAY"));
  dispname || FATAL (1, "X window is required.\n", NULL);
  bdfname ||  FATAL (0, "Usage: %s [-v] [-h] [BDFFILE | W+H+X-Y]\n", argv[0]);

  memset (hex, -1, 256);
  for (i = '0'; i <= '9'; i++) hex[i] = i - '0';
  for (i = 'A'; i <= 'F'; i++) hex[i] = i - 'A' + 10;
  for (i = 'a'; i <= 'f'; i++) hex[i] = i - 'a' + 10;
  hex['-'] = -2;

  if ((fd = open (bdfname, O_RDONLY)) >= 0)
    read_bdf (fd);
  else if (create_bdf (bdfname) < 0)
    FATAL (1, "Can't read file %s!\n", bdfname);
  sprintf (workdir, "%s.work", bdfname);
  if ((fd = open (workdir, O_RDONLY)) == -1 && mkdir (workdir, 0755) < 0)
    FATAL (1, "Can't make working directory %s.\n", workdir);
  close (fd);
  init_x (dispname);

  idx = cur_idx = min_idx;
  cur_page = PAGE_HEAD (min_idx);
  do {
    if (drawing_interrupted) draw_page (0);
    key = x_next_event (main_keysym_map, 0);
    show_message (NULL, 0);
  handle_event:
    switch (key) {
    case -1:		/* MotionNotify */
      idx = cur_idx;
      cur_idx = xy_idx (event.xbutton.x, event.xbutton.y);
      if (cur_idx == idx) continue;
      if (idx >= 0) draw_xbm (idx, 0, 0);
      else if (idx != INVALID_IDX) DRAW_MENU ((-1 - idx));
      if (cur_idx >= 0) draw_xbm (cur_idx, 0, 0);
      else if (cur_idx != INVALID_IDX) DRAW_MENU ((-1 - cur_idx));
      if (cur_idx < 0) X_Clear (INNER, INNER + FH + sep, BMP_WIDTH, FH);
      break;

    case -2: case -3:	/* ButtonPress */
      if (idx == INVALID_IDX) goto invalid_event;
      key = (idx < 0
	     ? menu[-1 - idx].key[event.xbutton.button != Button1]
	     : (key == -2 ? 'e' : 'g'));
      goto handle_event;

    case 'e':			/* Edit */
      if (cur_idx < 0 || !chartbl[cur_idx] || chartbl[cur_idx]->editing)
	goto invalid_event;
      chartbl[cur_idx]->editing = 1;
      draw_xbm (cur_idx, 0, 0);
      write_xbm (cur_idx);
      sprintf (linebuf, "%s %s/c%d.xbm &", BITMAP, workdir, cur_idx);
      system (linebuf);
      break;
	      
    case 'g':			/* Get or make */
      if (read_xbm (cur_idx) < 0 && !chartbl[cur_idx]) make_char (cur_idx);
      draw_xbm (cur_idx, 0, 0);
      break;

    case 'G':			/* Get all */
      for (i = min_idx; i <= max_idx; i++) read_xbm (i);
      draw_page (0);
      break;

    case 'u': case 'd': case 'l': case 'r': /* Shift glyph */
      if (cur_idx < 0 || !chartbl[cur_idx] || chartbl[cur_idx]->editing)
	goto invalid_event;
      if (max_space (key, cur_idx, cur_idx))
	shift_glyph (cur_idx, key);
      break;

    case 'U': case 'D': case 'L': case 'R': /* Shift glyph all */
      x = max_space (key = tolower (key), min_idx, max_idx);
      while (x-- > 0)
	for (i = min_idx; i < max_idx; i++)
	  if (chartbl[i] && !chartbl[cur_idx]->editing) shift_glyph (i, key);
      break;

    case 'b':			/* Bold */
      if (cur_idx < 0 || !chartbl[cur_idx] || chartbl[cur_idx]->editing)
	goto invalid_event;
      bold_glyph (cur_idx);
      break;

    case 'B':			/* Bold all */
      for (i = min_idx; i <= max_idx; i++)
	if (chartbl[i]) bold_glyph (i);
      draw_page (0);
      break;

    case 'i':			/* Italic */
      if (cur_idx < 0 || !chartbl[cur_idx] || chartbl[cur_idx]->editing)
	goto invalid_event;
      italic_glyph (cur_idx);
      break;

    case 'I':			/* Italic all */
      for (i = min_idx; i <= max_idx; i++)
	if (chartbl[i]) italic_glyph (i);
      draw_page (0);
      break;

    case 'x':			/* Delete */
      if (cur_idx < 0 || !chartbl[cur_idx]) goto invalid_event;
      delete_char (cur_idx);
      draw_xbm (cur_idx, 0, 0);
      break;

    case 'c':			/* Copy */
      if (cur_idx < 0 || !chartbl[cur_idx]) goto invalid_event;
      copy_idx = cur_idx;
      draw_xbm (copy_idx, MENU_X0, BMP_Y1 - sep - f.h);
      break;

    case 'y': case 'Y':		/* Yank */
      if (cur_idx < 0 || copy_idx < 0 || !chartbl[copy_idx])
	goto invalid_event;
      yank_char (copy_idx, cur_idx, key == 'Y');
      draw_xbm (cur_idx, 0, 0);
      break;

    case '<': case '>':		/* First or last page */
      cur_idx = key == '<' ? min_idx : max_idx;
      cur_page = PAGE_HEAD (cur_idx);
      draw_page (0);
      break;

    case 'n': case 'N': case 'p': case 'P': /* Previous or next page */
      if (next_or_prev (tolower (key) == 'n', isupper (key)) < 0)
	goto handle_event;
      break;

    case 'h': case '?':		/* Show help */
      show_help ();
      break;

    case 'w': case 'z':		/* Widen or shrink */
      if (cur_idx < 0 || !chartbl[cur_idx] || chartbl[cur_idx]->editing)
	goto invalid_event;
      ch = chartbl[cur_idx];
      if (key == 'w') regularize_bitmap (ch); else shrink_bitmap (ch);
      draw_xbm (cur_idx, 0, 0);
      break;

    case 'W': case 'Z':		/* Widen or shring all */
      for (i = min_idx; i <= max_idx; i++)
	if (ch = chartbl[i])
	  if (key == 'W') regularize_bitmap (ch); else shrink_bitmap (ch);
      draw_page (0);
      break;

    case 's':			/* Save */
      write_bdf ();
      break;

    case 'q': case 17:		/* Quit */
      if (!modified || x_y_or_n ("Exit without saving?")) end_flag = 1;
      break;

    case '!':			/* Show sample text */
      show_sample ();
      break;

    default:
    invalid_event:
      XBell (disp, 0);
    }
  } while (!end_flag);

  sprintf (linebuf, "rm -rf %s", workdir);
  system (linebuf);
  exit (0);
}
/* This is the 1000th line.  */
